﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;

namespace SuppKids.Core.Security
{
	public class XmlRsaManager
	{
		// ReSharper disable ConvertToConstant.Local
		private readonly string publicKey = "BgIAAACkAABSU0ExAAgAAAEAAQCJor8OmUsnowPUfyHQn+VN+rVE663z19gKjH8vs7gSCDhz87G6kgorzQQfpxLoYZpu3PQiNzrkIOmg1P3fVC04BGZzxzw32MtY1aorOk4ef5U8689vsdxBrhCNej1+EUbTQ02NKahyl6tAorzD4EigjXCYDSkRporsYzyUlv6EvV+uBSGzijmIP1RLPbCIBTe1cfmh242bzJjutY9LhxLBqnLNM1FIBfcWs1zdeVePZTV1zs39ol9W6H8fXmzZ8HF2WSkCdv1SV7YqS1LcuHGX6p4vHuimQTEWESp5n799POwNV+5TqdoRnVeWelsBa4PwVNmYTV6+nBKTgN4Ju0mT";
		// ReSharper restore ConvertToConstant.Local
		private readonly RSACryptoServiceProvider cryptoServiceProvider;

		public XmlRsaManager(RSACryptoServiceProvider cryptoServiceProvider)
		{
			this.cryptoServiceProvider = cryptoServiceProvider;
			this.cryptoServiceProvider.ImportCspBlob(Convert.FromBase64String(this.publicKey));
		}

		internal virtual bool VerifyXmlSignature(XmlDocument xmlDoc)
		{
			bool verificationResult = false;

			try
			{
				var signedXml = new SignedXml(xmlDoc);
				XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");

				if (nodeList.Count == 0)
				{
					throw new CryptographicException("Verification failed: No Signature was found in the document.");
				}

				if (nodeList.Count > 1)
				{
					throw new CryptographicException("Verification failed: More that one signature was found for the document.");
				}

				signedXml.LoadXml((XmlElement)nodeList[0]);

				verificationResult = signedXml.CheckSignature(this.cryptoServiceProvider);
			}
			catch (Exception ex)
			{
			}

			return verificationResult;
		}

		internal void Encrypt(XmlDocument xmlDocument, Stream targetStream)
		{
			using (var sourceStream = new MemoryStream())
			{
				xmlDocument.Save(sourceStream);
				this.Encrypt(sourceStream, targetStream);
			}
		}

		private void Encrypt(Stream sourceStream, Stream targetStream)
		{
			const int bufferSize = 32;
			this.Encrypt(sourceStream, targetStream, bufferSize);
		}

		private void Encrypt(Stream sourceStream, Stream targetStream, int bufferSize)
		{
			if (sourceStream.CanSeek)
			{
				sourceStream.Seek(0, SeekOrigin.Begin);
			}

			var buffer = new byte[bufferSize];
			int read;
			while ((read = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
			{
				var readBytes = new byte[read];
				Array.Copy(buffer, readBytes, read);
				byte[] bytes = this.cryptoServiceProvider.Encrypt(readBytes, false);
				targetStream.Write(bytes, 0, bytes.Length);
			}

			targetStream.Seek(0, SeekOrigin.Begin);
		}
	}
}
